﻿using DotNetty.Codecs.Http;
using DotNetty.Transport.Bootstrapping;
using DotNetty.Transport.Channels;
using DotNetty.Transport.Channels.Sockets;
using DotNetty.Transport.Libuv;
using System;
using System.Runtime;
using System.Runtime.InteropServices;
using System.Threading.Tasks;

namespace DotNetty.Wraper
{
    class WebSocketServerBuilder :
        BaseGenericServerBuilder<IWebSocketServerBuilder, IWebSocketServer, IWebSocketConnection, string>,
        IWebSocketServerBuilder
    {
        public WebSocketServerBuilder(int port, string path)
            : base(port)
        {
            if (string.IsNullOrWhiteSpace(path)) path = "/";
            _path = path;
        }
        private string _path { get; }
        public async override Task<IWebSocketServer> BuildAsync(Func<IWebSocketServer, IChannelHandler> addChannelHandler = null, Action<Transport.Channels.IChannelPipeline> OnPipelineAction = null
            , bool useLibuv = false, System.Security.Cryptography.X509Certificates.X509Certificate2 tlsCertificate = null)
        {
            return await BuildAsync(addChannelHandler, OnPipelineAction, useLibuv, tlsCertificate, 8192, 1);
        }

        public async Task<IWebSocketServer> BuildAsync(Func<IWebSocketServer, IChannelHandler> addChannelHandler = null, Action<Transport.Channels.IChannelPipeline> OnPipelineAction = null
            , bool useLibuv = false, System.Security.Cryptography.X509Certificates.X509Certificate2 tlsCertificate = null, int SoBacklog = 8192, int eventLoopCount = 1)
        {
            var tcpServer = new WebSocketServer(tlsCertificate != null, _port, _path, _event);

            if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
            {
                GCSettings.LatencyMode = GCLatencyMode.SustainedLowLatency;
            }
            tcpServer.UseSsl = tlsCertificate != null;

            IEventLoopGroup bossGroup;
            IEventLoopGroup workGroup;
            var bootstrap = new ServerBootstrap();
            if (useLibuv)
            {
                var dispatcher = new DispatcherEventLoopGroup();
                bossGroup = dispatcher;
                workGroup = new WorkerEventLoopGroup(dispatcher);
                bootstrap.Group(bossGroup, workGroup);

                bootstrap.Channel<TcpServerChannel>();
                if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)
                    || RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
                {
                    bootstrap
                        .Option(ChannelOption.SoReuseport, true)
                        .ChildOption(ChannelOption.SoReuseaddr, true);
                }
            }
            else
            {
                bossGroup = new MultithreadEventLoopGroup(eventLoopCount);
                workGroup = new MultithreadEventLoopGroup();
                bootstrap.Group(bossGroup, workGroup);

                bootstrap.Channel<TcpServerSocketChannel>();
            }

            IChannel bootstrapChannel = await bootstrap
                .Option(ChannelOption.SoBacklog, SoBacklog)
                .Option(ChannelOption.TcpNodelay, true)
                .ChildHandler(new ActionChannelInitializer<IChannel>(channel =>
                {
                    IChannelPipeline pipeline = channel.Pipeline;
                    if (tcpServer.UseSsl)
                    {
                        pipeline.AddLast(Handlers.Tls.TlsHandler.Server(tlsCertificate));
                    }
                    pipeline.AddLast(new HttpServerCodec());
                    pipeline.AddLast(new HttpObjectAggregator(65536));
                    OnPipelineAction?.Invoke(pipeline);

                    if (addChannelHandler != null)
                        pipeline.AddLast(addChannelHandler(tcpServer));
                    else
                        pipeline.AddLast(new CommonChannelHandler(tcpServer));
                })).BindAsync(_port);

            _event.OnServerStarted?.Invoke(tcpServer);

            tcpServer.SetChannel(bootstrapChannel);
            tcpServer.AfterClose = () =>
            {
                Task.WhenAll(
                   bossGroup.ShutdownGracefullyAsync(TimeSpan.FromMilliseconds(100), TimeSpan.FromSeconds(1)),
                   workGroup.ShutdownGracefullyAsync(TimeSpan.FromMilliseconds(100), TimeSpan.FromSeconds(1))).Wait();
            };

            return await Task.FromResult(tcpServer);
        }
    }
}